#include <tinx/vm86.h>
#include <tinx/fs.h>
#include <tinx/task.h>
#include <tinx/user.h>
#include <tinx/interrupt.h>
#include <tinx/memory.h>
#include <tinx/cpu.h>
#include <tinx/printk.h>
#include <tinx/global.h>
#include <tinx/debug.h>
#include <tinx/io.h>
#include <tinx/syscall.h>
#include <tinx/string.h>

#define LOGK(str, args...) DEBUGK(str, ##args)

#define VM86_ADDR 0x10000

enum
{
    OPCODE_INT = 0xCD,
    OPCODE_IRET = 0xCF,

    OPCODE_PUSHF = 0X9C,
    OPCODE_POPF = 0x9D,

    OPCODE_CLI = 0xFA,
    OPCODE_STI = 0xFB,

    OPCODE_IN_IMM_AL = 0xE4,
    OPCODE_IN_IMM_AX = 0xE5,
    OPCODE_IN_DX_AL = 0xEC,
    OPCODE_IN_DX_AX = 0xED,

    OPCODE_INSB = 0x6C,
    OPCODE_INSW = 0x6D,
    OPCODE_OUTSB = 0x6E,
    OPCODE_OUTSW = 0x6F,

    OPCODE_OUT_IMM_AL = 0xE6,
    OPCODE_OUT_IMM_AX = 0xE7,
    OPCODE_OUT_DX_AL = 0xEE,
    OPCODE_OUT_DX_AX = 0xEF,

    OPERAND_PREFIX = 0x66,
};

typedef struct vm86_state_t
{
    task_t *monitor;
    task_t *vmtask;
    vm86_reg_t *reg;
    vm86_reg_t *out;
    int vec;
} vm86_state_t;

typedef struct vmbin_t
{
    u32 magic;
    u8 code[];
} _packed vmbin_t;

static vmbin_t *vmbin = (vmbin_t *)VM86_ADDR;

static vm86_state_t vm86_state;
static vm86_state_t *state = &vm86_state;

// 进入 VM86 模式
void enable_vm86()
{
    asm volatile(
        "pushfl\n"
        "pop %eax\n"
        "orl $0x00020000, %eax\n"
        "push %eax\n"
        "popfl\n");
}

extern tss_t tss;

extern void interrupt_exit();

// 进入 VM86 模式
void vm86_enter()
{
    LOGK("vm86 enter ...\n");

    tss.esp0 = (u32)state->monitor + PAGE_SIZE;

    u32 addr = VM86_ADDR + PAGE_SIZE * 2;

    addr -= sizeof(vm86_reg_t);
    state->out = (vm86_reg_t *)addr;

    addr -= sizeof(u16);
    *(u16 *)addr = state->vec;

    *state->out = *state->reg;

    intr_frame_t *iframe = (intr_frame_t *)(tss.esp0 - sizeof(intr_frame_t));

    iframe->eax = 0;
    iframe->ebx = 0;
    iframe->ecx = 0;
    iframe->edx = 0;
    iframe->esi = 0;
    iframe->edi = 0;
    iframe->ebp = 0;

    iframe->ds = KERNEL_DATA_SELECTOR;
    iframe->es = KERNEL_DATA_SELECTOR;
    iframe->fs = KERNEL_DATA_SELECTOR;
    iframe->gs = KERNEL_DATA_SELECTOR;
    iframe->ss = VM86_ADDR >> 4;
    iframe->cs = VM86_ADDR >> 4;

    iframe->error = TINX_MAGIC;

    iframe->eflags = EFLAG_VM;
    iframe->eip = (u32)vmbin->code;
    iframe->esp = addr & 0xffff;

    asm volatile(
        "movl %0, %%esp\n"
        "jmp interrupt_exit\n" :: "g"(iframe));
}

// 调用 VM86 使用 BIOS 中断
int vm86_int(int vec, vm86_reg_t *reg)
{
    if (!state->monitor)
        return -EVM86;
    if (state->vmtask)
        return -EVM86;

    bool intr = interrupt_disable();

    while (state->monitor->state != TASK_BLOCKED)
    {
        task_yield();
    }

    task_t *task = running_task();

    state->vec = vec;
    state->reg = reg;
    state->vmtask = task;

    task_unblock(state->monitor, EOK);
    // 剩下的工作交给 monitor
    int ret = task_block(task, TASK_BLOCKED);

    state->vmtask = NULL;
    state->vec = 0;
    state->reg = NULL;

    set_interrupt_state(intr);

    return ret;
}

void vm86_thread()
{
    if (state->vmtask)
    {
        unmap_page(0, 1);
        task_unblock(state->vmtask, EOK);
    }

    // 等待 vmtask 唤醒
    task_block(state->monitor, TASK_BLOCKED);

    map_area(0, PAGE_SIZE);

    // 开始执行
    vm86_enter();
}

void vm86_handler(intr_frame_t *iframe)
{
    assert(iframe->eflags & EFLAG_VM)

    u8 *addr = (u8 *)(iframe->cs << 4) + iframe->eip;
    u16 opcode = addr[0];

    switch (opcode)
    {
    case OPCODE_INT:
        u8 vec = *(addr + 1);
        if (vec == 0xfe)
        {
            *state->reg = *state->out;
            vm86_thread();
        }
        iframe->esp -= 6;
        u32 ptr = (iframe->ss << 4) + iframe->esp;

        // int 指令压入
        *(u16 *)(ptr + 4) = iframe->eflags & 0xFFFF;
        *(u16 *)(ptr + 2) = iframe->cs;
        *(u16 *)(ptr) = (iframe->eip & 0xFFFF) + 2;

        iframe->eip = *(u16 *)(vec * 4);
        iframe->cs = *(u16 *)(vec * 4 + 2);
        break;
    case OPCODE_IRET:
        ptr = (iframe->ss << 4) + iframe->esp;

        iframe->eip = *(u16 *)ptr;
        iframe->cs = *(u16 *)(ptr + 2);
        iframe->eflags = (iframe->eflags & ~0xFFFF) | *(u16 *)(ptr + 4);

        iframe->esp += 6;
        break;
    case OPCODE_CLI:
        iframe->eflags &= ~EFLAG_IF;
        iframe->eip++;
        break;
    case OPCODE_PUSHF:
        iframe->esp -= 2;
        *(u16 *)((iframe->ss << 4) + iframe->esp) = (u16)iframe->eflags & 0xFFFF;
        iframe->eip++;
        break;
    case OPCODE_POPF:
        iframe->eflags = *(u16 *)((iframe->ss << 4) + iframe->esp) | (iframe->eflags & ~0xFFFF);
        iframe->esp += 2;
        iframe->eip++;
        break;
    case OPCODE_IN_DX_AL:
        iframe->eax = (iframe->eax & ~0xff) | inb(iframe->edx & 0xffff);
        iframe->eip++;
        break;
    case OPCODE_IN_DX_AX:
        iframe->eax = (iframe->eax & ~0xffff) | inw(iframe->edx & 0xffff);
        iframe->eip++;
        break;
    case OPCODE_OUT_DX_AX:
        outw(iframe->edx & 0xffff, iframe->eax & 0xffff);
        iframe->eip++;
        break;
    case OPCODE_OUT_DX_AL:
        outb(iframe->edx & 0xffff, iframe->eax & 0xff);
        iframe->eip++;
        break;
    default:
        panic("VM86 fault opcode 0x%X\n", opcode);
        break;
    }
}

void vm86_init()
{
    memset(state, 0, sizeof(vm86_state_t));

    fd_t fd = open("/bin/vm86.bin", O_RDONLY, 0755);
    if (fd < 0)
    {
        return;
    }

    stat_t statbuf;
    fstat(fd, &statbuf);

    read(fd, (char *)vmbin, statbuf.size);
    close(fd);

    if (vmbin->magic != TINX_MAGIC)
    {
        return;
    }

    state->monitor = task_create("vm86", 5, vm86_thread, KERNEL_USER);
}